package com.szjlc.mybatis.sqlinterceptor.util;

import cn.hutool.core.bean.BeanUtil;
import com.szjlc.mybatis.sqlinterceptor.entity.ExplainResultDTO;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

/**
 * @author steve
 * @since 2022-12-04 16:18
 */

@Component
public class SqlLogUtil {

    public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS");
    private static final Logger log = LoggerFactory.getLogger(SqlLogUtil.class);
    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private Map<String, String> exceptionResultMap = new HashMap<>(16);

    // private static final String DEFAULT_SQL_LOG_SHOW_SWITCH = "false";

    @Value("${explainSqlLogShowSwitch:true}")
    private Boolean explainSqlLogShowSwitch;

    @Resource
    private JdbcTemplate jdbcTemplate;

    public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) {
        String sql = showSql(configuration, boundSql);
        StringBuilder builder = new StringBuilder(100);
        builder.append(sqlId);
        builder.append("^ZHIKE^");
        builder.append(sql);
        builder.append("^ZHIKE^");
        builder.append(time);
        builder.append("ms");
        return builder.toString();
    }

    private static String getParameterValue(Object obj) {
        String value;
        if (obj instanceof String) {
            value = "'" + obj + "'";
        }
        else if (obj instanceof Date) {
            value = "'" + SIMPLE_DATE_FORMAT.format(obj) + "'";
        }
        else if (obj != null) {
            value = obj.toString();
        }
        else {
            value = "";
        }

        return value;
    }

    public static String showSql(Configuration configuration, BoundSql boundSql) {
        try {
            Object parameterObject = boundSql.getParameterObject();
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
            if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
                TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
                if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
                }
                else {
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    Iterator<ParameterMapping> iterator = parameterMappings.iterator();

                    while (iterator.hasNext()) {
                        ParameterMapping parameterMapping = (ParameterMapping) iterator.next();
                        String propertyName = parameterMapping.getProperty();
                        Object obj;
                        if (metaObject.hasGetter(propertyName)) {
                            obj = metaObject.getValue(propertyName);
                            sql = sql.replaceFirst("\\?", getParameterValue(obj));
                        }
                        else if (boundSql.hasAdditionalParameter(propertyName)) {
                            obj = boundSql.getAdditionalParameter(propertyName);
                            sql = sql.replaceFirst("\\?", getParameterValue(obj));
                        }
                    }
                }
            }

            return sql;
        }
        catch (Exception e) {
            log.error("异常{}", e.getMessage(), e);
            return "";
        }
    }

    @Async("taskExecutor")
    public void printSql2Log(String sqlId, BoundSql boundSql, Configuration configuration, String date, long time) {
        String sql = getSql(configuration, boundSql, sqlId, time);
        String printSql = sql.split("(\\^ZHIKE\\^)")[0] + "\n"
                + "#执行SQL==>"
                + (new PrettyFormatterSqlUtil()).getPrettySql(sql.split("(\\^ZHIKE\\^)")[1])
                + ";\n";

        String explainResult;
        if (Boolean.TRUE.equals(explainSqlLogShowSwitch)) {
            if (exceptionResultMap.containsKey(boundSql.getSql())) {
                explainResult = exceptionResultMap.get(boundSql.getSql());
            }
            else {
                String explainSql = "EXPLAIN " + printSql.split("#执行SQL==>")[1];
                Map<String, Object> explainMap = jdbcTemplate.queryForMap(explainSql);
                ExplainResultDTO explainResultDTO = BeanUtil.mapToBean(explainMap, ExplainResultDTO.class, true, null);
                explainResult = explainResultDTO.toString().split("\\[")[1];
                exceptionResultMap.put(boundSql.getSql(), explainResult);
            }

            log.info("#[{}] [thread={}] 开始执行sql==>{}#<==执行总耗时【{}ms】\n# region explain执行计划:[{}\n# endregion\n",
                    date, Thread.currentThread().getName(), printSql, time, explainResult);
        }
        else {
            // vm开关，执行计划sql不打印
            log.info("#[{}] [thread={}] 开始执行sql==>{}#<==执行总耗时【{}ms】\n",
                    date, Thread.currentThread().getName(), printSql, time);
        }
    }
}
